﻿
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
	/// <summary>
	/// Timimng
	/// </summary>
	/*
        Implementation based on the information contained here:
        http://www.cpcwiki.eu/index.php/765_FDC
        and here:
        http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
    */
	public partial class NECUPD765
	{
		/// <summary>
		/// The current Z80 cycle
		/// </summary>
		private long CurrentCPUCycle
		{
			get
			{
				if (_machine == null)
					return 0;
				else
					return _machine.CPU.TotalExecutedCycles;
			}
		}

		/// <summary>
		/// The last CPU cycle when the FDC accepted an IO read/write
		/// </summary>
		private long LastCPUCycle;

		/// <summary>
		/// The current delay figure (in Z80 t-states)
		/// This implementation only introduces delay upon main status register reads
		/// All timing calculations should be done during the other read/write operations
		/// </summary>
		private long StatusDelay;

		/// <summary>
		/// Defines the numbers of Z80 cycles per MS
		/// </summary>
		private long CPUCyclesPerMs;

		/// <summary>
		/// The floppy drive emulated clock speed
		/// </summary>
		public const double DriveClock = 31250;

		/// <summary>
		/// The number of floppy drive cycles per MS
		/// </summary>
		public long DriveCyclesPerMs;

		/// <summary>
		/// The number of T-States in one floppy drive clock tick
		/// </summary>
		public long StatesPerDriveTick;

		/// <summary>
		/// Responsible for measuring when the floppy drive is ready to run a cycle
		/// </summary>
		private long TickCounter;

		/// <summary>
		/// Internal drive cycle counter
		/// </summary>
		private int DriveCycleCounter = 1;

		/// <summary>
		/// Initializes the timing routines
		/// </summary>
		private void TimingInit()
		{
			// z80 timing
			double frameSize = _machine.GateArray.FrameLength;
			double rRate = _machine.GateArray.Z80ClockSpeed / frameSize;
			long tPerSecond = (long)(frameSize * rRate);
			CPUCyclesPerMs = tPerSecond / 1000;

			// drive timing
			double dRate = DriveClock / frameSize;
			long dPerSecond = (long)(frameSize * dRate);
			DriveCyclesPerMs = dPerSecond / 1000;

			long TStatesPerDriveCycle = (long)(_machine.GateArray.Z80ClockSpeed / DriveClock);
			StatesPerDriveTick = TStatesPerDriveCycle;
		}

		/// <summary>
		/// Called by reads to the main status register
		/// Returns true if there is no delay
		/// Returns false if read is to be deferred
		/// </summary>
		private bool CheckTiming()
		{
			// get delta
			long delta = CurrentCPUCycle - LastCPUCycle;

			if (StatusDelay >= delta)
			{
				// there is still delay remaining
				StatusDelay -= delta;
				LastCPUCycle = CurrentCPUCycle;
				return false;
			}
			else
			{
				// no delay remaining
				StatusDelay = 0;
				LastCPUCycle = CurrentCPUCycle;
				return true;
			}
		}
	}
}
